package com.element.oauth2.server.authorization;

import com.element.oauth2.constant.SecurityParams;
import com.element.oauth2.server.global.converter.*;
import com.element.oauth2.server.global.filter.OAuthAuthorizationEndpointFilter;
import com.element.oauth2.server.global.generator.OAuthAccessTokenGenerator;
import com.element.oauth2.server.global.generator.OAuthRefreshTokenGenerator;
import com.element.oauth2.server.global.provider.*;
import com.element.oauth2.server.handler.*;
import com.element.oauth2.server.service.IAuthClientDetailsService;
import com.element.oauth2.server.service.IAuthUserDetailsService;
import com.element.oauth2.server.service.IGrantTypeService;
import com.element.oauth2.token.RedisOAuth2AuthorizationConsentService;
import com.element.oauth2.token.RedisOAuth2AuthorizationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 认证服务器配置
 */
@Configuration
@Import({RedisOAuth2AuthorizationService.class, RedisOAuth2AuthorizationConsentService.class})
public class AuthorizationServerConfiguration {

    @Autowired
    private IGrantTypeService grantTypeService;

    @Autowired
    private IAuthUserDetailsService authUserDetailsService;

    @Autowired
    private IAuthClientDetailsService authClientDetailsService;

    @Autowired
    private OAuth2AuthorizationService oAuth2AuthorizationService;

    @Autowired
    private OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService;

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {

        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(authUserDetailsService);
        provider.setUserDetailsPasswordService(authUserDetailsService);
        http.authenticationProvider(provider);

        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.exceptionHandling(exceptions -> {
            // 未通过身份验证时处理逻辑
            exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(SecurityParams.LOGIN_PAGE));
        });

        // 使用 HttpSecurity 获取 OAuth 2.1 配置中的 OAuth2AuthorizationServerConfig 对象
        OAuth2AuthorizationServerConfigurer authorizationServerConfig = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        authorizationServerConfig.tokenEndpoint(tokenEndpoint -> { // 认证配置
            tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
                    .accessTokenResponseHandler(new TokenAuthenticationSuccessHandler()) // 登录成功处理器
                    .errorResponseHandler(new TokenErrorAuthenticationFailureHandler())// 登录失败处理器
                    .authenticationProvider(new ParamAuthenticationProvider(passwordEncoder(), authUserDetailsService,
                            oAuth2AuthorizationService, oAuth2TokenGenerator()));// 添加自定义参数登陆
        }).authorizationEndpoint(authorizationEndpoint -> { // 授权配置
            // 授权码端点个性化confirm页面
            authorizationEndpoint.consentPage(SecurityParams.CONSENT_PAGE);
            authorizationEndpoint.authorizationRequestConverter(authorizationRequestConverter());
            authorizationEndpoint.errorResponseHandler(new AuthErrorAuthenticationFailureHandler());

        }).clientAuthentication(clientAuthentication -> { // 客户端认证配置
            clientAuthentication.authenticationConverter(new GlobalClientAuthenticationConverter())
                    .errorResponseHandler(new ClientErrorAuthenticationFailureHandler());
        }).tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> {
            tokenIntrospectionEndpoint.authenticationProvider(new OAuthTokenIntrospectionAuthenticationProvider(authClientDetailsService, oAuth2AuthorizationService));
            tokenIntrospectionEndpoint.introspectionResponseHandler(new TokenIntrospectionSuccessHandler());
        }).tokenRevocationEndpoint(tokenRevocationEndpoint -> {
            tokenRevocationEndpoint.authenticationProvider(new OAuthTokenRevocationAuthenticationProvider(oAuth2AuthorizationService));
            tokenRevocationEndpoint.revocationResponseHandler(new TokenRevocationSuccessHandler());
        });

        http.addFilterBefore(new OAuthAuthorizationEndpointFilter(authorizationProviderManager(), authorizationRequestConverter()), AbstractPreAuthenticatedProcessingFilter.class);

        return authorizationServerConfig
                .authorizationService(oAuth2AuthorizationService)// redis存储token的实现
                .authorizationConsentService(oAuth2AuthorizationConsentService)
                .authorizationServerSettings(AuthorizationServerSettings.builder()
                        // 设置授权码请求端点
                        .authorizationEndpoint(SecurityParams.AUTHORIZATION_ENDPOINT_URL)
                        // 设置token请求端点
                        .tokenEndpoint(SecurityParams.TOKEN_ENDPOINT_URL)
                        // 验证token有效性
                        .tokenIntrospectionEndpoint(SecurityParams.TOKEN_INTROSPECTION_URL)
                        // 注销token
                        .tokenRevocationEndpoint(SecurityParams.TOKEN_REVOCATION_URL)
                        .build())
                .and()
                .build();
    }

    /**
     * 令牌生成规则实现
     * client:username:uuid
     *
     * @return OAuth2TokenGenerator
     */
    @Bean
    public OAuth2TokenGenerator<?> oAuth2TokenGenerator() {
        return new DelegatingOAuth2TokenGenerator(new OAuthAccessTokenGenerator(oAuth2AuthorizationService),
                new OAuthRefreshTokenGenerator(oAuth2AuthorizationService));
    }

    /**
     * 注入加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * request -> authorization 注入请求转换器
     * 可以自定义请求参数
     */
    private AuthenticationConverter authorizationRequestConverter() {
        List<AuthenticationConverter> converterList = new ArrayList<>();
        converterList.add(new OAuthAuthorizationCodeConverter());
        converterList.add(new OAuthAuthorizationCodeRequestConverter());
        converterList.add(new OAuthAuthorizationConsentRequestConverter());
        return new DelegatingAuthenticationConverter(converterList);
    }

    private ProviderManager authorizationProviderManager() {
        OAuth2AuthorizationCodeAuthenticationProvider codeProvide = new OAuth2AuthorizationCodeAuthenticationProvider(oAuth2AuthorizationService, oAuth2TokenGenerator());
        OAuthCodeRequestAuthorizationProvider codeRequestProvide = new OAuthCodeRequestAuthorizationProvider(authClientDetailsService, oAuth2AuthorizationService, oAuth2AuthorizationConsentService);
        OAuthConsentRequestAuthenticationProvider ConsentProvide = new OAuthConsentRequestAuthenticationProvider(authClientDetailsService, oAuth2AuthorizationService, oAuth2AuthorizationConsentService);
        return new ProviderManager(codeProvide, codeRequestProvide, ConsentProvide);
    }

    /**
     * request -> accessToken 注入请求转换器
     * 可以自定义请求参数
     */
    private AuthenticationConverter accessTokenRequestConverter() {
        List<AuthenticationConverter> converterList = new ArrayList<>();
        converterList.add(new OAuth2RefreshTokenAuthenticationConverter());
        converterList.add(new ParamAuthenticationConverter(AuthorizationGrantType.PASSWORD));
        if (!CollectionUtils.isEmpty(grantTypeService.initGrantTypes())) {
            for (String grantType : grantTypeService.initGrantTypes()) {
                converterList.add(new ParamAuthenticationConverter(new AuthorizationGrantType(grantType)));
            }
        }
        return new DelegatingAuthenticationConverter(converterList);
    }
}